/*
 * TMap Designer - A Thematic Map design software
 * Copyright (C) 2006 World Health Organization
 * http://www.who.int
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package who.tmap.toolkit.cdr.impl;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

import who.tmap.bo.cd.CDDotDensity;
import who.tmap.bo.cd.CartographicDisplay;
import who.tmap.bo.cd.DotDensityDistributions;
import who.tmap.common.TMapException;
import who.tmap.toolkit.MappingToolKit;
import who.tmap.toolkit.shape.MapShape;
import who.tmap.toolkit.shape.PolygonShape;

public class CDRDotDensityImpl extends BaseCDRImpl {

    private static Logger logger = Logger.getLogger(CDRDotDensityImpl.class);
    
    private static final int MAX_NB_ITERATIONS =30;
    private static final int MAX_POINTS =500;
    
    private CDDotDensity ps;
    private Map<String, Double> surfacesPerJoinId;
	
	public void init( MappingToolKit mtk, CartographicDisplay cd, double mapFactor ) throws TMapException {
		super.init( mtk, cd, mapFactor );
	
		ps =(CDDotDensity)cd;
		if ( ps.getDistribution().equals( DotDensityDistributions.Distribs.PROPORTIONAL ) ) {
			computeSurfaces();
		}
	}
	
	/**
	 * Computes the surface for all the polygons with the same join id
	 */
	private void computeSurfaces() {
		surfacesPerJoinId =new HashMap<String, Double>();
		
		for( MapShape mapShape : mtk.getShapes() ) {
			if ( mapShape instanceof PolygonShape ) {
				PolygonShape polygonShape =(PolygonShape)mapShape;
				Double totalSurface =surfacesPerJoinId.get( polygonShape.getName() );
				
				Double surface =polygonShape.getBounds().getWidth() * polygonShape.getBounds().getHeight();
				
				if ( totalSurface == null ) {
					totalSurface =surface;
				} else {
					totalSurface+=surface;
				}
				
				surfacesPerJoinId.put( polygonShape.getName(), totalSurface );
			}
		}
	}
    
	public void represent( Graphics2D g, MapShape mapShape ) throws TMapException {

		Color prevColor =g.getColor();
		g.setColor( ps.getColor() );
		
		long nbPoints =computeNbPoints( mapShape, ps.getNbUnitsPerSymbol() );
		double size =computeSymbolSize( mapShape, ps.getPointSize() );
		
		Rectangle2D bounds =mapShape.getBounds();
		
		// Returns the number of col/row
		long nbCol =Math.round( Math.sqrt( nbPoints ) );
		long nbRow =nbCol;
		
		double colWidth =bounds.getWidth() / (double)nbCol;
		double rowHeight =bounds.getHeight() / (double)nbRow;
		
		double offsetX =0, offsetY =0;
		
		int iterationCt =1;
		long drawnPoints =0;
		long row, col;
		double x, y;

		while ( drawnPoints < nbPoints ) {
			
			for( long pointCt =0; pointCt < nbPoints; pointCt++ ) {
					
				row =pointCt / nbCol;
				col =pointCt - ( row * nbCol );
				
				x =bounds.getMinX() + ( (double)col * colWidth ) + offsetX;
				y =bounds.getMinY() + ( (double)row * rowHeight ) + offsetY;
	
				if ( mapShape.contains( new Point2D.Double( x, y ) ) ) {
				
					Ellipse2D circle =new Ellipse2D.Double( x - (size/2), y - (size/2),	size, size );		
					
					g.draw( circle );
				
					g.setColor( ps.getColor() );
					g.fill( circle );
					
					if ( ++drawnPoints >= nbPoints ) {
						break;
					}
				} // if
			} // for
			
			if ( iterationCt++ > MAX_NB_ITERATIONS ) {
				logger.debug("Max iterations reached for shape [" + mapShape.toString() + "]: " + drawnPoints + " points drawn instead of " + nbPoints + " expected points" );
				break;
			}
			
			offsetX =colWidth / (double)iterationCt;
			offsetY =rowHeight / (double)iterationCt;
		} 
					
		g.setColor( prevColor );

	}
	
	private long computeNbPoints( MapShape mapShape, int nbUnitsPerSymbol ) {
		double val =getDoubleVal( mapShape );
		long nbPoints =0;
		
		if ( val != java.lang.Double.MIN_VALUE ) {
			double dbl =val / (double)nbUnitsPerSymbol;
			nbPoints =Math.round( dbl );
		} 
		
		// The number of points depends on the surface of the polygon compared to the rest
		// of polygons width the same join id
		if ( ps.getDistribution().equals( DotDensityDistributions.Distribs.PROPORTIONAL ) ) {
			PolygonShape polygonShape =(PolygonShape)mapShape;

			Double surface =polygonShape.getBounds().getWidth() * polygonShape.getBounds().getHeight();
			Double totalSurface =surfacesPerJoinId.get( polygonShape.getName() );
			
			if ( totalSurface != 0 ) {
				nbPoints =Math.round( ((double)nbPoints * (surface/totalSurface)) );
			}
		}
		
		if ( nbPoints > MAX_POINTS ) {
			logger.debug("Cannot draw " + nbPoints + " points for shape [" + mapShape.toString() + "]: limited to " + MAX_POINTS + " points.");
			
			nbPoints =MAX_POINTS;
		}
		
		return nbPoints;
	}
		
	private double computeSymbolSize( MapShape mapShape, int pointSize ) {
		double size =0;
		if ( pointSize != 0 ) {
			size=( 1.d*((double)pointSize) ) / mapFactor;
		} else {
			size =0;
		}

		return size;
	}

}
